﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MyFramework.Common;
using MyFramework.Common.Interfaces;

namespace MyFramework.Command.DelegateWatchCommand
{
    /// <summary>
    /// Raise trigger
    /// </summary>
    public class RaiseTrigger : IRaiseTrigger
    {
        #region Fields

        /// <summary>
        /// The chain of property to watch.
        /// </summary>
        protected List<PropertyWatchChain> chains;

        /// <summary>
        /// The property to raise.
        /// </summary>
        protected Expression<Func<object>>[] propertiesToRaise;

        /// <summary>
        /// The instance of all the property.
        /// </summary>
        protected IEntity source;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseTrigger"/> class.
        /// </summary>
        /// <param name="propertyToRaise">The property to raise.</param>
        protected RaiseTrigger(Expression<Func<object>> propertyToRaise)
        {
            this.propertiesToRaise = new Expression<Func<object>>[] { propertyToRaise };
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseTrigger"/> class.
        /// </summary>
        /// <param name="propertiesToRaise">The properties to raise.</param>
        protected RaiseTrigger(Expression<Func<object>>[] propertiesToRaise)
        {
            this.propertiesToRaise = propertiesToRaise;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseTrigger"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="propertiesToWatch">The properties to watch.</param>
        /// <param name="propertiesToRaise">The properties to raise.</param>
        public RaiseTrigger(IEntity source, Expression<Func<IEntity, object>>[] propertiesToWatch, Expression<Func<object>>[] propertiesToRaise)
            : this(propertiesToRaise)
        {
            this.chains = propertiesToWatch.Select(e => MakePropertyWatchChain(e)).ToList();
            this.Source = source;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyWatch"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="propertyToRaise">The property to raise.</param>
        /// <param name="propertiesToWatch">The properties to watch.</param>
        public RaiseTrigger(IEntity source, Expression<Func<object>> propertyToRaise, params Expression<Func<IEntity, object>>[] propertiesToWatch)
            : this(source, propertiesToWatch, new Expression<Func<object>>[] { propertyToRaise })
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseTrigger"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="propertyToWatch">The property to watch.</param>
        /// <param name="propertiesToRaise">The properties to raise.</param>
        public RaiseTrigger(IEntity source, Expression<Func<IEntity, object>> propertyToWatch, params Expression<Func<object>>[] propertiesToRaise)
            : this(source, new Expression<Func<IEntity, object>>[] { propertyToWatch }, propertiesToRaise)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public IEntity Source
        {
            get { return source; }
            set
            {
                if (source != value)
                {
                    source = value;
                    foreach (var x in chains)
                        x.Head.Source = source;
                }
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Makes the property watch chain.
        /// </summary>
        /// <param name="expr">The expr.</param>
        /// <returns></returns>
        private PropertyWatchChain MakePropertyWatchChain(Expression<Func<IEntity, object>> expr)
        {
            var ret = PropertyWatchChain.FromLambda(expr);
            ret.WatchedPropertyChanged += new EventHandler<EventArgs>(ret_WatchedPropertyChanged);
            return ret;
        }

        /// <summary>
        /// Handles the WatchedPropertyChanged event of the ret control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void ret_WatchedPropertyChanged(object sender, EventArgs e)
        {
            foreach (var propertyToRaise in this.propertiesToRaise)
            {
                var unaryValue = propertyToRaise.Body as UnaryExpression;
                dynamic t = unaryValue != null ? unaryValue.Operand : propertyToRaise.Body;
                source.RaisePropertyChangedEvent(t.Member.Name);
            }
        }

        #endregion
    }

    /// <summary>
    /// It defines a dependency between a property to raise and a chain of properties to watch.
    /// When one of the properties in the chain is changed, a RaisePropertyChanged is called on the property to raise.
    /// </summary>
    /// <typeparam name="T">The source type.</typeparam>
    public class RaiseTrigger<T> : RaiseTrigger
        where T : IEntity
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseTrigger&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="propertiesToWatch">The properties to watch.</param>
        /// <param name="propertiesToRaise">The properties to raise.</param>
        public RaiseTrigger(T source, Expression<Func<T, object>>[] propertiesToWatch, Expression<Func<object>>[] propertiesToRaise)
            : base(propertiesToRaise)
        {
            this.chains = propertiesToWatch.Select(e => MakePropertyWatchChain(e)).ToList();
            this.Source = source;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyWatch"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="propertyToRaise">The property to raise.</param>
        /// <param name="propertiesToWatch">The properties to watch.</param>
        public RaiseTrigger(T source, Expression<Func<object>> propertyToRaise, params Expression<Func<T, object>>[] propertiesToWatch)
            : this(source, propertiesToWatch, new Expression<Func<object>>[] { propertyToRaise })
        {
            this.chains = propertiesToWatch.Select(e => this.MakePropertyWatchChain(e)).ToList();
            this.Source = source;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RaiseTrigger&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="propertyToWatch">The property to watch.</param>
        /// <param name="propertiesToRaise">The properties to raise.</param>
        public RaiseTrigger(T source, Expression<Func<T, object>> propertyToWatch, params Expression<Func<object>>[] propertiesToRaise)
            : this(source, new Expression<Func<T, object>>[] { propertyToWatch }, propertiesToRaise)
        {
            this.chains = new List<PropertyWatchChain>() { this.MakePropertyWatchChain(propertyToWatch) };
            this.Source = source;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the source.
        /// </summary>
        /// <value>
        /// The source.
        /// </value>
        public new T Source
        {
            get { return (T)source; }
            set { base.Source = value; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Makes the property watch chain.
        /// </summary>
        /// <param name="expr">The expr.</param>
        /// <returns></returns>
        private PropertyWatchChain MakePropertyWatchChain(Expression<Func<T, object>> expr)
        {
            var ret = PropertyWatchChain.FromLambda(expr);
            ret.WatchedPropertyChanged += new EventHandler<EventArgs>(ret_WatchedPropertyChanged);
            return ret;
        }

        #endregion
    }
}
